from khronos.utils import INF, Logger, Clock

class LocalSearch(object):
    def __init__(self):
        pass
        
    def solve(self, instance, cpu=INF, iterations=INF, improvements=INF, 
              upper_bound=INF, optimal=-INF):
        self.init(instance, upper_bound, optimal)
        return self.run(cpu, iterations, improvements)
        
    def init(self, instance=None, upper_bound=INF, optimal=-INF):
        """Initialize the multi-start procedure."""
        if instance is not None:
            self.instance = instance
        self.upper_bound = upper_bound
        self.optimal = optimal
        # +++
        self.solutions = []
        self.metadata = {"Metaheuristic": self.__class__.__name__, 
                         "Instance": self.instance, 
                         "Objective (optimal)": optimal, 
                         "Objective (initial upper bound)": upper_bound}
        # +++
        self.running = False
        self.cpu = Clock()
        self.cpu_limit = INF
        self.iteration = 0
        self.iteration_limit = INF
        self.improvement_limit = INF
        self.log(" --- %s initialized --- " % (self.__class__.__name__,))
        
    def run(self, step_on_improvement=False):
        solution = self.start()
        cost = (self.objective(solution), 1.0)
        best_solution = None
        best_cost = (INF, 1.0)
        noise_fnc = self.rng.random
        while cost < best_cost:
            best_solution = solution
            best_cost = cost
            for neighbor in self.neighbors(solution):
                neighbor_cost = (self.objective(neighbor), noise_fnc())
                if neighbor_cost < cost:
                    cost = neighbor_cost
                    solution = neighbor
                    if step_on_improvement:
                        break
        return best_solution
        
    # --------------------------------------------------------------------------
    def initial_solution(self):
        raise NotImplementedError()
        
    def neighbors(self, solution):
        raise NotImplementedError()
        
    def objective(self, solution):
        raise NotImplementedError()
        
